<?php

/**
 * Yabsta News
 *
 * This file is a subject under proprietary sofrware license
 * 
 * @category
 * @package
 * @subpackage
 * @author      valerii lazarevych <lazarevuch@gmail.com>
 * @copyright   Copyright(c) Valerii Lazarevych
 * @license     proprietary software license
 * @version     $Id: IndexController.php 26.05.2012 18:29:33 valerii $
 */

abstract class Model
{                
    /**
     * Initialized PDO object
     * Take from AppRegistry('databes')
     * 
     * @var PDO 
     */
    private     static $pdo         =   null;
    
    /**
     * Working sql statement
     * 
     * @var array like self::$_sqlPattern
     */
    protected $sqlState             =   null;
                                                
    /**
     * Prepared SELECT PDO statement
     * 
     * @static
     * @var PDOStatement 
     */
    protected   $select             =   null;
    
    /**
     * INSERT Prepared PDO statement
     * 
     * @static
     * @var PDOStatement 
     */
    protected   $insert             =   null;
    
    /**
     * UPDATE Prepared PDO statement
     * 
     * @static
     * @var PDOStatement 
     */
    protected   $update             =   null;
    
    /**
     * DELETE Prepared PDO statement
     * 
     * @static
     * @var PDOStatement 
     */
    protected   $delete             =   null;
    
    /**
     * Working table name
     * 
     * @var string $tableName
     */
    protected   $tableName          =   null;        
    
    /**
     * Model own domain class name suffix
     * 
     * @var string
     */
    protected   $domainObjSuffix    =   'domainObjects_';
    
    /**
     * Object use in object relation mapping
     * 
     * @var domainObject $domainObject
     */
    protected   $domainObject       =   null;   
    
    /**
     * MySql Select Statement pattern
     * 
     * @var array
     */
    protected static $sqlStatePattern   =   array(  '1select' => 'SELECT ',
                                                    '2from'   => 'FROM ',
                                                    '4where'  => ''   );
        
    /**
     * Create pdo prepared statements
     * 
     * @todo finish with insert prepared statement
     * 
     * @param string $tableName
     * @param domain models object $obj
     */
    public function __construct()
    {                                                                                                                               
        $tableName        =   $this->getTableName();
        $this->select    =   self::$pdo->prepare('SELECT * FROM '.$tableName. ' WHERE id = :id');        
        //$this-> update    =   self::$pdo->prepare('UPDATE * SET  '.$tableName. ' WHERE id = :id');                                            
        $this->delete    =   self::$pdo->prepare('DELETE   FROM '.$tableName. ' WHERE id = :id');
    }        
    
    /**
     * Set db adapter for a model
     * 
     * @param type $adapter 
     */
    public static function setDbAdapt($adapter)
    {
        self::$pdo  =   $adapter;
        
        return self::$pdo;
    }

    /**
     * Get table name we will working on
     * 
     * @return string $this->table_name
     */   
    protected function getTableName()
    {
        if ( !$this->tableName ){
            
            $nameParts          = explode('_', get_class($this));             
            $this->tableName    = strtolower( $nameParts[1] );        }
            
        return $this->tableName;                
    }
    
    /**
     * Get domainObject ot use in ORM
     * 
     * @return domainObject
     */
    protected function getDomainObject()
    {        
        if ( !$this->domainObject ){            
              $domainObjName           =   $this->domainObjSuffix . ucfirst($this->getTableName());                                     
              $this->domainObject      =   new $domainObjName;
        }        
        return $this->domainObject;
    }       
    
    /**
     * Initialize default SQL query 
     * 
     * @param string $fields
     * @return Model 
     */
    public function select($fields = '*')
    {
        $this->sqlState     =   self::$sqlStatePattern;
        
        $this->sqlState['1select']  .=   ' '.$fields.' ';        
        $this->from();
        
        return $this;
    }
    
    /**
     * Select objects like own
     * 
     * @param domainObject $obj
     * @return Model
     */
    public function selectLike(domainObject $obj)
    {
        $this->select();        
        $semaphore      =   0;
        
        foreach( get_object_vars($obj) as $name => $value) {
            
             if ($value !== '' && $value !== null) {
                
                if ($semaphore++)       //  have more then 1 where statement use AND logic
                    $this->where('AND');                
                $this->where( $name .'='. (is_string($value) ? "'".$value."'" : $value) );      }}
                                                    
        return $this;
    }

    /**
     * Add SQL from statement to query
     * 
     * @param mixed $tablename
     * @return Model 
     */
    public function from($tablename = null)
    {                
        if ($tablename  === null){    
            $tablename  =   $this->getTableName();   }
            
        $this->sqlState['2from']    .=   is_array($tablename) ? implode(',', $tablename) : $tablename;                   
        return $this;
    }

    /**
     * Add SQL where statement to query string
     * 
     * @param string $where
     * @return Model 
     */
    public function where($where, $operator = null)
    {
        if ($this->sqlState['4where'] === '') {            
            $this->sqlState['4where']  =    'WHERE'; }
            
        $this->sqlState['4where']   .=   ' '
                                         //.($this->sqlState['4where'] != false && in_array(strtolower($operator), array('and','or','not')) ? $operator.' ' : '')
                                         .(string)$where;
        return $this;
    }
    
    /**
     * Add SQL order by statement to query string
     * 
     * @param string $order_field
     * @param string $sort  ASC|DESC
     * @return Model 
     */
    public function order($order_field, $sort)
    {
        $this->sqlState['5order_by']    =   'ORDER BY '. $order_field.' '.$sort;
        return $this;
    }
    
    /**
     * Add Sql Limit Statement to query string
     * 
     * @param int $start
     * @param int $end
     * @return Model 
     */
    public function limit($start, $end = null)
    {
        $this->sqlState['6limit']   =   'LIMIT '. (int)$start
                                                . ($end  ? ','. (int)$end : '');
        return $this;
    }
    
    /**
     * Add SQL left join statement to query string
     * 
     * @param string $tablename
     * @param string $condition
     * @return Model 
     */
    public function leftJoin($tablename, $condition)
    {        
        $this->sqlState['3leftJoin']     =   'LEFT JOIN '. $tablename.' ON '.$condition;        
        return $this;
    }
    
    /**
     * Add SQL right join statement to query string
     * 
     * @param string $tablename
     * @param string $condition
     * @return Model 
     */
    public function rightJoin($tablename, $condition)
    {
        $this->sqlState['3rightJoin']    =   'RIGHT JOIN '. $tablename.' ON '.$condition;
        return $this;
    }
    
    /**
     * Add SQL inner join statement to query string
     * 
     * @param string $tablename
     * @param string $condition
     * @return Model 
     */
    public function innerJoin($tablename, $condition)
    {
        $this->sqlState['3innerJoin']    =   'INNER JOIN '. $tablename.' ON '.$condition;
        return $this;
    }
    
    /**
     * Add SQL outter join statement to query string
     * 
     * @param string $tablename
     * @param string $condition
     * @return Model 
     */
    public function outJoin($tablename, $condition)
    {
        $this->sqlState['3outJoin']      =   'OUTTER JOIN '. $tablename.' ON '.$condition;
        return $this;
    }
    
    /**
     * Execute prepared time before sql query string
     * 
     * @param array $bindParams
     * @return array of $this->domainObject 
     */
    public function execute(array $bindParams = null)
    {                                             
        $state  =   self::$pdo->prepare( $this->getSqlString() );                        
        
        return $state->execute($bindParams) ?  $this->_rowsFetch($state)   :   array();
    }        
    
    /**
     * Mapping selected rows to array of $this->domainObject
     * 
     * @todo    can not fetch more then 10 rows to objs from 11 query
     * 
     * @param PDOStatement $rows
     * @param string $className
     * @return array of classname objects $aRes
     */
    private function _rowsFetch($rows)
    {                                   
        // Use table name to get row model object name
        $domainObjectName    =   get_class($this->getDomainObject());
        $i  =   0;
        
        //var_dump($_SERVER['REQUEST_URI']);  //  /application/index.php?cmd=links                
        while ( ($obj    =   $rows->fetchObject($domainObjectName))  ) {            
                $i++;
                $aRes[]  =   $obj;
        }   
                
        return isset($aRes) ? $aRes : array();
    }
    
    /**
     * Get sql query string
     * 
     * @return string
     */
    protected function getSqlString()
    {
        ksort($this->sqlState);
        //echo implode(' ', $this->sqlState);
        
        return implode(' ', $this->sqlState);
    }
    
    /**
     * Set select prepare pdo statement
     * to use it at $this->find()
     * 
     * @param string $sqlState 
     */
    protected function setFindState($sqlState)
    {
        $this->select   =   self::$pdo->prepare($sqlState);
    }
    
    /**
     * Looking for object at database based on id
     * 
     * @return domainObject
     */
    public function find(domainObject $obj)
    {                                            
        if ( $this->select->execute(array( ':id' => $obj->getId())))    {
            
            $objsFound     =   $this->_rowsFetch($this->select);
            $objFound      =   !isset($objsFound[0] ) ? new stdClass()  :  $objsFound[0];
                       
            foreach( get_object_vars($obj) as $key  =>  $value) {                                                        
                $obj->$key   =    property_exists($objFound, $key) ? $objFound->$key : $obj->$key;        }        }                
    }
    
    /**
     * Map object to table as a new row
     * 
     * @return inserted row id
     */
    public function insert(domainObject $obj)
    {                
        foreach (get_object_vars($obj) as $name => $value) {            
            
            if ($value !== null){  
                
                    $fields[]   =   $name;
                    $values[]   =   $bindName = ':'.$name;
                    
                    if (!is_array($value))
                        $bindValues[$bindName]  =   $value instanceof domainObject  ?   $value->$name() :   $value;                  }       }
                        
        $insertState    =   self::$pdo->prepare('INSERT INTO '. $this->getTableName().
                                                '('.implode(',', $fields).')'.                                                 
                                                 ' VALUES('. implode(',', $values).')');                
        $insertState->execute($bindValues);        
        $obj->setId( self::$pdo->lastInsertId());                
                
        return self::$pdo->lastInsertId();
    }
    
    /**
     * Update object row in table     
     * 
     * @return bool
     */
    public function update(domainObject $obj)    
    {                
        foreach (get_object_vars($obj) as $name => $value) {            
            
            if ($value !== null){
                
                    $equations[]   =   $name == 'password' ? $name.'= password(:'.$name.')' :  $name.' = :'.$name;
                    
                    if (!is_array($value))
                        $bindValues[':'.$name]  =   $value instanceof domainObject  ?   $value->$name() :   $value;                  }                               
        }                       
        $insertState    =   self::$pdo->prepare('UPDATE '. $this->getTableName().
                                                ' SET '.implode(',', $equations).
                                                ' WHERE id = :id');
        
        $outcome    =   $insertState->execute($bindValues+array(':id' => $obj->getId()));        
                
        return $outcome;
    }
    
    /**
     * Remove obj row from table
     * 
     * @return bool
     */
    public function remove(domainObject $obj)
    {
        return  $this->delete->execute( array(':id' => $obj->getId()) );
    }
    
    
}
